/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2009 Miranda ICQ/IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "commonheaders.h"
#include "profilemanager.h"
#include <sys/stat.h>

#define WM_INPUTCHANGED (WM_USER + 0x3000)
#define WM_FOCUSTEXTBOX (WM_USER + 0x3001)

typedef BOOL (__cdecl *ENUMPROFILECALLBACK) (TCHAR * fullpath, TCHAR * profile, LPARAM lParam);

//u: db plugin list
struct DBPEnumData {
    TCHAR** dbNames;
    DATABASELINK** dbLinks;
	int dbCount;
};

struct DlgProfData {
	PROPSHEETHEADER * psh;
	HWND hwndOK;			// handle to OK button
	PROFILEMANAGERDATA * pd;
	HANDLE hFileNotify;
};

struct DetailsData {
	struct DlgProfData * prof;
	struct DBPEnumData * dbed;
};

struct ProfileEnumData {
    HWND hwnd;
    TCHAR* szProfile;
};

extern TCHAR mirandabootini[MAX_PATH]; 

char **GetSeviceModePluginsList(void);
void SetServiceModePlugin( int idx );

//u: Check on Aero iface
static BOOL IsAeroMode()
{
	BOOL result;
	return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result;
}

static void ThemeDialogBackground(HWND hwnd)
{
	if (enableThemeDialogTexture)
		enableThemeDialogTexture(hwnd, ETDT_ENABLETAB);
}

//u: checking profile name on invalid characters
static int validProfileName(TCHAR* szProfileName)
{
	int res = 1, 
		j = 0, 
		len = _tcslen(szProfileName);
	TCHAR* validName = (TCHAR*)mir_alloc(sizeof(TCHAR) * (len + 1));
	for (int i = 0; i < len; i++)
	{
		if (_tcschr(_T(".?/\\#' "), szProfileName[i]) == 0)
			validName[j++] = szProfileName[i];
	}
	validName[j] = _T('\0');
	if(_tcscmp(szProfileName, validName) != 0)
	{
		_tcscpy(szProfileName, validName);
		res = 0;
	}
	mir_free(validName);
	return res;
}

static int findProfiles(TCHAR * szProfileDir, ENUMPROFILECALLBACK callback, LPARAM lParam)
{
	// find in Miranda IM profile subfolders
	HANDLE hFind = INVALID_HANDLE_VALUE;
	WIN32_FIND_DATA ffd;
	TCHAR searchspec[MAX_PATH];
	mir_sntprintf(searchspec, SIZEOF(searchspec), _T("%s\\*.*"), szProfileDir);
	hFind = FindFirstFile(searchspec, &ffd);
	if ( hFind == INVALID_HANDLE_VALUE )
		return 0;

	do {
		// find all subfolders except "." and ".."
		if ( (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && _tcscmp(ffd.cFileName, _T(".")) && _tcscmp(ffd.cFileName, _T("..")) ) {
			TCHAR buf[MAX_PATH], profile[MAX_PATH];
			mir_sntprintf(buf, SIZEOF(buf), _T("%s\\%s\\%s.dat"), szProfileDir, ffd.cFileName, ffd.cFileName);
			if (_taccess(buf, 0) == 0) {
				mir_sntprintf(profile, SIZEOF(profile), _T("%s.dat"), ffd.cFileName);
				if ( !callback(buf, profile, lParam ))
					break;
			}
		}
	}
		while ( FindNextFile(hFind, &ffd) );
	FindClose(hFind);

	return 1;
}

static void RemoveProfile(HWND hwndList, int iItem, DlgProfData* dat)
{
	if (iItem < 0)
		return;

	TCHAR profile[MAX_PATH], profilef[MAX_PATH*2];

	ComboBox_GetText(hwndList, profile, MAX_PATH);
	mir_sntprintf(profilef, SIZEOF(profilef), TranslateT("Are you sure you want to remove profile \"%s\"?"), profile);

	if (IDYES != MessageBox(NULL, profilef, _T("Miranda IM"), MB_YESNO | MB_TASKMODAL | MB_ICONWARNING))
		return;

	mir_sntprintf(profilef, SIZEOF(profilef), _T("%s\\%s%c"), dat->pd->szProfileDir, profile, 0);

	SHFILEOPSTRUCT sf = {0};
	sf.wFunc = FO_DELETE;
	sf.pFrom = profilef;
	sf.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_ALLOWUNDO;
	SHFileOperation(&sf);
	
	ComboBox_DeleteString(hwndList, iItem);
	ComboBox_SetText(hwndList, NULL);
}

static void DropButtonMenu(HWND hwndDlg, POINT pt, DetailsData* dat)
{
	HMENU hMenu =  CreatePopupMenu();
	HMENU hSubMenu = CreatePopupMenu();
	if(!dat->prof->pd->newProfile)
	{
		int i = 0;
		AppendMenu(hMenu,  MF_STRING,  90000, TranslateT("Remove profile"));
		char **list = GetSeviceModePluginsList();
		if (list)
		{
			AppendMenu(hMenu,  MF_SEPARATOR,  NULL, NULL);
			AppendMenu(hMenu, MF_POPUP | MF_STRING, (UINT)hSubMenu, TranslateT("Start in Service Mode with"));

			LRESULT index;
			while (list[i]) 
			{
				TCHAR *str = LangPackPcharToTchar(list[i]);
				AppendMenu(hSubMenu, MF_STRING, i++ + 90100, str);
				mir_free(str);
			}
			mir_free(list);
		}
	}
	else
	{
		AppendMenu(hMenu, MF_POPUP | MF_STRING, (UINT)hSubMenu, TranslateT("Create DataBase with"));
		for (int i = 0; i < dat->dbed->dbCount; i++)
			//u: why it not work in release?
			if( dat->dbed->dbNames[i])
				AppendMenu(hSubMenu, MF_STRING, i + 90100, dat->dbed->dbNames[i]);
	}
	//u: swow menu
	int index = TrackPopupMenu(
		hMenu, 
		TPM_RETURNCMD,
		pt.x, 
		pt.y, 0, 
		GetDlgItem(hwndDlg, IDOK), 
		NULL);
	if (index == 90000)
	{
		if (!dat->prof->pd->noProfiles) 
			RemoveProfile(GetDlgItem(hwndDlg, IDC_PROFILES), ComboBox_GetCurSel(GetDlgItem(hwndDlg, IDC_PROFILES)), dat->prof);
	}
	if (index >= 90100)
	{
		if(!dat->prof->pd->newProfile)
		{
			SetServiceModePlugin(index - 90100);
			SendMessage(hwndDlg, WM_COMMAND, IDOK, 0);
		}
		else
		{
			dat->prof->pd->dblink = dat->dbed->dbLinks[index - 90100];
			makeDatabase(dat->prof->pd->szProfile, dat->prof->pd->dblink, hwndDlg);
		}
		//SendMessage(hwndDlg, WM_COMMAND, IDOK, 0);
	}

	DestroyMenu(hSubMenu); 
	DestroyMenu(hMenu); 
}

static LRESULT CALLBACK OkButtonProc(HWND hBtn, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
	switch (msg)
	{
		case WM_RBUTTONDOWN:
			{  
				POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
				//u! - get some split on XP button?
				ClientToScreen(hBtn, &pt);
				DropButtonMenu(GetParent(hBtn), pt, (DetailsData*)GetProp(hBtn, L"DATA_STRUCT"));
			}
			break;
	}
	return DefSubclassProc(hBtn, msg, wParam, lParam);
}

static int FindDbProviders(const char*, DATABASELINK * dblink, LPARAM lParam)
{
	DBPEnumData* dbpe = (DBPEnumData*)lParam;
	
	char szName[64];
	int i = 0;

	dbpe->dbCount++;

	mir_realloc(dbpe->dbNames, sizeof(TCHAR*) * dbpe->dbCount);
	mir_realloc(dbpe->dbLinks, sizeof(DATABASELINK*) * dbpe->dbCount);

	if ( dblink->getFriendlyName(szName,SIZEOF(szName),1) == 0 ) 
	{
		dbpe->dbNames[dbpe->dbCount - 1] = LangPackPcharToTchar(szName);
		dbpe->dbLinks[dbpe->dbCount - 1] = dblink;
	}
	return DBPE_CONT;
}

static int DetectDbProvider(const char*, DATABASELINK * dblink, LPARAM lParam)
{
	int error;

#ifdef _UNICODE
	char* fullpath = makeFileName(( TCHAR* )lParam );
#else
	char* fullpath = (char*)lParam;
#endif

	int ret = dblink->grokHeader(fullpath, &error);
#ifdef _UNICODE
	mir_free( fullpath );
#endif
	if ( ret == 0) {
#ifdef _UNICODE
		char tmp[ MAX_PATH ];
		dblink->getFriendlyName(tmp, SIZEOF(tmp), 1);
		MultiByteToWideChar(CP_ACP, 0, tmp, -1, (TCHAR*)lParam, MAX_PATH);
#else
		dblink->getFriendlyName((TCHAR*)lParam, MAX_PATH, 1);
#endif
		return DBPE_HALT;
	}

	return DBPE_CONT;
}

BOOL EnumProfilesForList(TCHAR * fullpath, TCHAR * profile, LPARAM lParam)
{
	//u: need clean
	ProfileEnumData *ped = (ProfileEnumData*)lParam;
	HWND hwndList = GetDlgItem(ped->hwnd, IDC_PROFILES);

	TCHAR sizeBuf[64];
	int iItem=0;
	struct _stat statbuf;
	bool bFileExists = false, bFileLocked = true;

	TCHAR* p = _tcsrchr(profile, '.');
	_tcscpy(sizeBuf, _T("0 KB"));
	if ( p != NULL ) *p=0;

	COMBOBOXEXITEM  item = { 0 };
	item.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
	item.pszText = profile;
	item.iItem = 0;

	if ( _tstat(fullpath, &statbuf) == 0) {
		if ( statbuf.st_size > 1000000 ) {
			mir_sntprintf(sizeBuf,SIZEOF(sizeBuf), _T("%.3lf"), (double)statbuf.st_size / 1048576.0 );
			_tcscpy(sizeBuf+5, _T(" MB"));
		}
		else {
			mir_sntprintf(sizeBuf,SIZEOF(sizeBuf), _T("%.3lf"), (double)statbuf.st_size / 1024.0 );
			_tcscpy(sizeBuf+5, _T(" KB"));
		}
		bFileExists = TRUE;

		bFileLocked = !fileExist(fullpath);
	}

	item.iImage = item.iSelectedImage = bFileLocked;

	iItem = SendMessage( hwndList, CBEM_INSERTITEM, 0, (LPARAM)&item );
	if ( lstrcmpi(ped->szProfile, fullpath) == 0 )
		ComboBox_SetCurSel(hwndList, iItem);

	return TRUE;
}

static INT_PTR CALLBACK DlgProfileManager(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	struct DetailsData* dat = (struct DetailsData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
	HWND hwndList = GetDlgItem(hwndDlg, IDC_PROFILES);
	HWND hBtn = NULL;
	HIMAGELIST hImgList = NULL;

	switch (msg) 
	{
		case WM_INITDIALOG:
		{
			struct DlgProfData * prof = (struct DlgProfData *)lParam;
			PROPSHEETHEADER *psh = prof->psh;
			TranslateDialogDefault(hwndDlg);
			SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadImage(hMirandaInst, MAKEINTRESOURCE(IDI_USERDETAILS),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0));
			SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadImage(hMirandaInst, MAKEINTRESOURCE(IDI_MIRANDA),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYICON),0));
			dat = (struct DetailsData*)mir_alloc(sizeof(struct DetailsData));
			dat->prof = prof;
			prof->hwndOK = GetDlgItem( hwndDlg, IDOK );
			EnableWindow( prof->hwndOK, FALSE );
			SetWindowLongPtr( hwndDlg, GWLP_USERDATA, (LONG_PTR)dat );

			//u: Watch on profile name or write changes
			dat->prof->hFileNotify = FindFirstChangeNotification(
				dat->prof->pd->szProfileDir, 
				TRUE, 
				FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE);
			if (dat->prof->hFileNotify != INVALID_HANDLE_VALUE)
				SetTimer(hwndDlg, 0, 1200, NULL);

			//u: New OK button
			if ((DWORD)(LOBYTE(LOWORD(GetVersion())) <= 5))
			{
				hBtn = CreateWindow(L"Button", L"Run", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE , 1, 260, 202, 23, hwndDlg, (HMENU)IDOK, hMirandaInst, 0);	
				SetWindowSubclass(hBtn, OkButtonProc, 0, 0);
				SetProp(hBtn, L"DATA_STRUCT", (HANDLE)dat);
			}
			else
				hBtn = CreateWindow(L"Button", L"Run", BS_DEFSPLITBUTTON | WS_CHILD | WS_VISIBLE , 1, 260, 202, 23, hwndDlg, (HMENU)IDOK, hMirandaInst, 0);	

			//u: Load icons in combobox
			hImgList = ImageList_Create(16, 16, ILC_MASK | (IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16), 2, 1);
			ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_USERDETAILS));
			ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_DELETE));
			SendMessage(hwndList, CBEM_SETIMAGELIST, 0, (LPARAM)hImgList);

			//u: find all profiles and fill combobox
			ProfileEnumData ped = { hwndDlg, dat->prof->pd->szProfile };
			findProfiles(dat->prof->pd->szProfileDir, EnumProfilesForList, (LPARAM)&ped);
			if(ComboBox_GetCount(hwndList))
				ComboBox_SetCurSel(hwndList, 0);

			//u: find all db driver plugins
			dat->dbed = (struct DBPEnumData*)mir_alloc(sizeof(struct DBPEnumData));
			dat->dbed->dbCount = 0;
			dat->dbed->dbNames = (TCHAR**)mir_alloc(sizeof(TCHAR*));
			dat->dbed->dbLinks = (DATABASELINK**)mir_alloc(sizeof(DATABASELINK*));
			//
			PLUGIN_DB_ENUM dbe;
			dbe.cbSize = sizeof(dbe);
			dbe.pfnEnumCallback = (int(*)(const char*,void*,LPARAM))FindDbProviders;
			dbe.lParam = (LPARAM)dat->dbed;
			CallService(MS_PLUGINS_ENUMDBPLUGINS, 0, ( LPARAM )&dbe);

			return TRUE;
		}
		case WM_CTLCOLORSTATIC:
			switch ( GetDlgCtrlID(( HWND )lParam )) {
			case IDC_WHITERECT:
				SetBkColor(( HDC )wParam, GetSysColor( COLOR_WINDOW ));
				return ( INT_PTR )GetSysColorBrush( COLOR_WINDOW );
			}
			break;

		case WM_NOTIFY:
			switch (((LPNMHDR)lParam)->code)
			{
				case BCN_DROPDOWN:
				{
					NMBCDROPDOWN* pDropDown = (NMBCDROPDOWN*)lParam;
					if (pDropDown->hdr.hwndFrom == GetDlgItem(hwndDlg, IDOK))
					{
						POINT pt;
						GetCursorPos((LPPOINT)&pt);
						DropButtonMenu(hwndDlg, pt, dat);
						return TRUE;
					}
				}
				break;
			
			}
			break;

		case WM_COMMAND:
			switch(LOWORD(wParam)) 
			{
				//u: update OK Button state
				//u: set profile name in szProfile
				case IDC_PROFILES:
					if ( HIWORD(wParam) == CBN_EDITCHANGE )
					{
						TCHAR* profile = (TCHAR*)mir_alloc(sizeof(TCHAR) * ComboBox_GetTextLength(hwndList));
						ComboBox_GetText(hwndList, profile, MAX_PATH);
						if(!validProfileName(profile))
							ComboBox_SetText(hwndList, profile);
						if(_tcslen(profile) && !(dat->prof->pd->newProfile && (dat->dbed->dbCount == 0)))
						{
							EnableWindow(dat->prof->hwndOK, TRUE );
							dat->prof->pd->newProfile = ComboBox_FindStringExact(hwndList, 0, profile) == CB_ERR;
							{
								TCHAR tmpPath[MAX_PATH];
								mir_sntprintf(tmpPath, SIZEOF(tmpPath), _T("%s\\%s.dat"), dat->prof->pd->szProfileDir, profile);
								HANDLE hFile = CreateFile(tmpPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
								if (hFile == INVALID_HANDLE_VALUE)
									mir_sntprintf(dat->prof->pd->szProfile, MAX_PATH, _T("%s\\%s\\%s.dat"), dat->prof->pd->szProfileDir, profile, profile);
								else
									_tcscpy(dat->prof->pd->szProfile, tmpPath);
								CloseHandle(hFile);
							}
						}
						else
							EnableWindow(dat->prof->hwndOK, FALSE );
 					}
					break;

				case IDCANCEL:
					EndDialog(hwndDlg,0);
					break;

				case IDOK:
					{
						if(dat->prof->pd->newProfile)
						{
							dat->prof->pd->dblink = dat->dbed->dbLinks[0];
							makeDatabase(dat->prof->pd->szProfile, dat->prof->pd->dblink, hwndDlg);
						}
						EndDialog(hwndDlg,1);
						break;
				}	
			}
			break;

		//u: Nya-things ^^ 
		case WM_PAINT:
			{
				PAINTSTRUCT pnt;
				HDC hDC = BeginPaint(hwndDlg, &pnt);
				
				//u: draw glass background
				if (IsAeroMode()) 
				{
					RECT rc = { 0 };
					GetClientRect(hwndDlg, &rc);
					rc.bottom = 150;

					FillRect(hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));

					MARGINS margins = {0};
					margins.cyTopHeight = rc.bottom;
					dwmExtendFrameIntoClientArea(hwndDlg, &margins);
				}

				//u: load icon
				HICON hIcon = (HICON)LoadImage(
						hMirandaInst,
						MAKEINTRESOURCE(IDI_BIG_LOGO),
						IMAGE_ICON,
						128,128,
						0);

				//u: draw icon
				DrawIconEx(
					hDC, 
					35, 5, 
					hIcon,
					128, 128,
					0, 0,
					DI_NORMAL
				);

				DestroyIcon(hIcon);
				EndPaint(hwndDlg, &pnt);
			}
			break;

		case WM_TIMER:
			if (WaitForSingleObject(dat->prof->hFileNotify, 0) == WAIT_OBJECT_0)
			{
				while(ComboBox_GetCount(hwndList))
					ComboBox_DeleteString(hwndList, 0);
				ProfileEnumData ped = { hwndDlg, dat->prof->pd->szProfile };
				findProfiles(dat->prof->pd->szProfileDir, EnumProfilesForList, (LPARAM)&ped);
				FindNextChangeNotification(dat->prof->hFileNotify);
				if (!(dat->prof->pd->newProfile && (dat->dbed->dbCount == 0)))
					EnableWindow(dat->prof->hwndOK, ComboBox_GetCurSel(hwndList)==1);
			}
			break;

		case WM_DESTROY:
			DestroyIcon(( HICON )SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, 0));
			DestroyIcon(( HICON )SendMessage(hwndDlg, WM_SETICON, ICON_BIG, 0));
			
			//u: clear all
			KillTimer(hwndDlg, 0);
			FindCloseChangeNotification(dat->prof->hFileNotify);
			ImageList_Destroy(hImgList);
			DestroyWindow(hBtn);
			if ((DWORD)(LOBYTE(LOWORD(GetVersion())) <= 5))
				RemoveProp(hBtn, L"DATA_STRUCT");
			for (int i = 0; i < dat->dbed->dbCount; i++)
			{
				mir_free(dat->dbed->dbNames[i]);
				mir_free(dat->dbed->dbLinks[i]);
			}
			mir_free(dat->dbed->dbNames);
			mir_free(dat->dbed->dbLinks);
			mir_free(dat->dbed);
			mir_free(dat);
			break;
	}
	return FALSE;
}

int getProfileManager(PROFILEMANAGERDATA * pd)
{
	DlgProfData prof;
	prof.pd  = pd;

	int rc = DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(IDD_PROFILEMANAGER),NULL,DlgProfileManager,(LPARAM)&prof);

	return rc;
}
